#include <QDebug>
#include <thread>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include "V4l2Device.h"


V4l2Device v4lDev("/dev/video0");


extern "C" int GetV4lBuf(struct v4l2_buffer *buf)
{
    return (v4lDev.GetFrame(*buf))?1:-1;
}

extern "C" void RefV4lBuf(struct v4l2_buffer *buf)
{
    v4lDev.ReleaseBuf(*buf);
}

//extern "C" int getFdV4l()
//{
//    return v4lDev.m_fdV4l;
//}

V4l2Device::V4l2Device(const std::string devName)
{
    m_devName = devName;
}

V4l2Device::~V4l2Device()
{
    CloseDevice();
}

bool V4l2Device::InitDevice()
{
    struct v4l2_capability cap;
    if (ioctl (m_fdV4l, VIDIOC_QUERYCAP, &cap) < 0)//查询视频设备的能力，比如是否具有视频输入,或者音频输入输出等
    {
        if (EINVAL == errno)
        {
            fprintf (stderr, "%s is no V4L2 device\n", m_devName.c_str());
        }
        else
        {
            fprintf (stderr, "%s isn not V4L device,unknow error\n", m_devName.c_str());

        }
        return false;
    }
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) //V4L2_CAP_VIDEO_CAPTURE 是否支持图像获取
    {
        fprintf (stderr, "%s is no video capture device\n", m_devName.c_str());
        return false;
    }
    if (!(cap.capabilities & V4L2_CAP_STREAMING))  //是否支持Streaming能力
    {
        fprintf (stderr, "%s does not support streaming i/o\n", m_devName.c_str());
        return false;
    }
    struct v4l2_dbg_chip_ident chip;
    if (ioctl(m_fdV4l, VIDIOC_DBG_G_CHIP_IDENT, &chip))
    {
        printf("VIDIOC_DBG_G_CHIP_IDENT failed.\n");
        close(m_fdV4l);
        return false;
    }
    printf("TV decoder chip is %s\n", chip.match.name);

    int g_input = 1;
    if (ioctl(m_fdV4l, VIDIOC_S_INPUT, &g_input) < 0)//G_INPUT和S_INPUT用来查询和选则当前的input
    {
        printf("VIDIOC_S_INPUT failed\n");
        close(m_fdV4l);
        return false;
    }
    v4l2_std_id id;
    if (ioctl(m_fdV4l, VIDIOC_G_STD, &id) < 0) //VIDIOC_G_STD就是获得当前输入使用的standard，不过这里只是得到了该标准的id
    {
        printf("VIDIOC_G_STD failed\n");
        close(m_fdV4l);
        return false;
    }
//    v4l2_std_id m_currentStd = id;
    if (ioctl(m_fdV4l, VIDIOC_S_STD, &id) < 0)//设置视频的制式，制式包括PAL/NTSC
    {
        printf("VIDIOC_S_STD failed\n");
        close(m_fdV4l);
        return false;
    }
    /* Select video input, video standard and tune here. */
    struct v4l2_cropcap cropcap;
    memset(&cropcap, 0, sizeof(cropcap));
    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl (m_fdV4l, VIDIOC_CROPCAP, &cropcap) < 0)
    {
        struct v4l2_crop crop;
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect; /* reset to default */

        if (ioctl (m_fdV4l, VIDIOC_S_CROP, &crop) < 0)//设置视频图像的采集窗口的大小
        {
            switch (errno)
            {
                case EINVAL:
                    /* Cropping not supported. */
                    fprintf (stderr, "%s  doesn't support crop\n",
                        m_devName.c_str());
                    break;
                default:
                    /* Errors ignored. */
                    break;
            }
        }
    }
    else
    {
        /* Errors ignored. */
    }
    struct v4l2_streamparm parm;
    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    parm.parm.capture.timeperframe.numerator = 1;
    parm.parm.capture.timeperframe.denominator = 0;
    parm.parm.capture.capturemode = 0;
    if (ioctl(m_fdV4l, VIDIOC_S_PARM, &parm) < 0)//设置视频的帧率
    {
        printf("VIDIOC_S_PARM failed\n");
        close(m_fdV4l);
        return false;
    }
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = 0;
    fmt.fmt.pix.height      = 0;
//    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    if (ioctl (m_fdV4l, VIDIOC_S_FMT, &fmt) < 0)//帧的格式
    {
        fprintf (stderr, "%s iformat not supported \n", m_devName.c_str());
        return false;
    }
    /* Note VIDIOC_S_FMT may change width and height. */
    /* Buggy driver paranoia. */
    unsigned int min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
    {
        fmt.fmt.pix.bytesperline = min;
    }

    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
    {
        fmt.fmt.pix.sizeimage = min;
    }
    if (ioctl(m_fdV4l, VIDIOC_G_FMT, &fmt) < 0) //读取当前驱动的频捕获格式
    {
        printf("VIDIOC_G_FMT failed\n");
        close(m_fdV4l);
        return false;
    }
    m_frameWidth = fmt.fmt.pix.width;
    m_frameHeight = fmt.fmt.pix.height;
//    unsigned int g_frame_size = fmt.fmt.pix.sizeimage;
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof (req));
    req.count               = BUF_NUM;//申请帧缓冲数量BUF_NUM
    req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory              = V4L2_MEMORY_MMAP;

    if (ioctl (m_fdV4l, VIDIOC_REQBUFS, &req) < 0) //申请帧缓冲
    {
        if (EINVAL == errno)
        {
            fprintf (stderr, "%s does not support memory mapping\n", m_devName.c_str());
            return false;
        } else
        {
            fprintf (stderr, "%s does not support memory mapping, unknow error\n", m_devName.c_str());
            return false;
        }
    }
//    if (req.count < 2)
    if (req.count < BUF_NUM)
    {
        fprintf (stderr, "Insufficient buffer memory on %s\n", m_devName.c_str());
        return false;
    }
    return true;
}

bool V4l2Device::InitMmap()
{
    struct v4l2_buffer buf;
    for (unsigned int i = 0; i < BUF_NUM; i++)
    {
        memset(&buf, 0, sizeof (buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(m_fdV4l, VIDIOC_QUERYBUF, &buf) < 0)//获取到对应index的缓存信息，查询到帧缓冲区在内核空间的长度和偏移量地址,此处主要利用length信息及offset信息来完成后面的mmap操作。
        {
                printf("VIDIOC_QUERYBUF error\n");
                return false;
        }

        printf("length=%d\n",buf.length);
        m_mmapMemorys[i].length = buf.length;
        m_mmapMemorys[i].offset = (size_t) buf.m.offset;
        m_mmapMemorys[i].start = (unsigned char *)mmap (NULL, m_mmapMemorys[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, m_fdV4l, m_mmapMemorys[i].offset);
        memset(m_mmapMemorys[i].start, 0xFF, m_mmapMemorys[i].length);
    }
    for (unsigned int i = 0; i < BUF_NUM; i++)
    {
        memset(&buf, 0, sizeof (buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        buf.m.offset = m_mmapMemorys[i].offset;
        if (ioctl (m_fdV4l, VIDIOC_QBUF, &buf) < 0) //将申请到的帧缓冲全部放入视频采集输出队列，以便存放采集的数据
        {
            printf("VIDIOC_QBUF error\n");
            return false;
        }
//        QueueBuf(i);
    }    
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl (m_fdV4l, VIDIOC_STREAMON, &type) < 0)//开始视频流数据的采集
    {
        printf("VIDIOC_STREAMON error\n");
        return false;
    }
    return true;
}

bool V4l2Device::OpenDevice()
{
    struct stat st;
    if(-1 == stat(m_devName.c_str(),&st)) //通过文件名获取文件信息，并保存在结构体stat中
    {
        fprintf(stderr,"Cannot identify '%s':%d, %s\n",m_devName.c_str(),errno,strerror(errno));
        return false;
    }
    if ((m_fdV4l = open(m_devName.c_str(), O_RDWR, 0)) < 0)//打开视频设备文件
    {
        fprintf(stderr,"Unable to open %s\n", m_devName.c_str());
        return false;
    }
    return true;
}

bool V4l2Device::Init()
{
    if(!OpenDevice())
    {
        printf("device open failed!\n");
        return false;
    }
    printf("open v4l device success!\n");
    if(!InitDevice())
    {
        return false;
    }
    if(!InitMmap())
    {
        return false;
    }
    printf("device initialize success!\n");
    return true;
}

void V4l2Device::StartReadFrame()
{
    if(!Init())
    {
        return;
    }
    std::thread([&]()
    {
        while(true)
        {
            if(!DequeueBuf())
            {
                printf("BufferRefresh error\n");
                break;
            }
        }
    }).detach();
}

bool V4l2Device::Dqbuf(v4l2_buffer &buf)
{
    memset(&buf, 0, sizeof(v4l2_buffer));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    //取出FIFO输出队列中已经采样的帧缓存,若输出队列无数据则阻塞，一直等到一帧数据采集完成
    //取出一帧数据后，驱动程序接着采集新的一帧数据
    if (ioctl(m_fdV4l, VIDIOC_DQBUF, &buf)< 0)
    {
//        printf("VIDIOC_DQBUF failed.\n");
        qDebug() <<"VIDIOC_DQBUF failed.";
        return false;
    }
    m_isBufferHasData = true;
    return true;
}

bool V4l2Device::DequeueBuf()
{
    v4l2_buffer buf;
    if(!Dqbuf(buf))//
    {
        return false;
    }
    m_dqedBufsMtx.lock();//接下去使用m_dqedBufs容器，不允许其他线程修改
    int index = m_dqedBufs.back().index;//m_dqedBufs容器中存放的当前最新一帧缓存
    if(m_dqedBufs.size()>=1)
    {        
        m_bufRefCountMtx[index].lock();
        if(m_bufRefCount[index]>=1)
        {
            m_bufRefCount[index]--;//得到新的数据后，之前的数据默认引用计数减去1，若其他地方不再持有，则不久之后释放
        }
        m_bufRefCountMtx[index].unlock();
    }
    index = buf.index;
    m_bufRefCountMtx[index].lock();
    m_bufRefCount[index]++;//最新得到的数据，引用计数加1，默认需要被使用一段时间
    m_bufRefCountMtx[index].unlock();
    m_dqedBufs.push_back(std::move(buf));
    m_dqedBufsMtx.unlock();
    Qbufs();//新数据加入，是否需要从容器中清除，清除后的缓存重新加入V4L输入队列
    return true;
}

bool V4l2Device::GetFrame(v4l2_buffer &buf,cv::Mat &matFrame)
{
    if(!m_isBufferHasData)
    {
        qDebug() <<"BufferHasNoData";
        return false;
    }
    if(!GetFrame(buf))
    {
        return false;
    }
    matFrame = cv::Mat(m_frameHeight, m_frameWidth, CV_8UC1, m_mmapMemorys[buf.index].start);
    return true;
}

bool V4l2Device::GetFrame(v4l2_buffer &buf)
{
    if(!m_isBufferHasData)
    {
        qDebug() <<"BufferHasNoData";
        return false;
    }
    m_dqedBufsMtx.lock();
    buf = m_dqedBufs.back();
    int index = buf.index;
    m_bufRefCountMtx[index].lock();
    m_bufRefCount[index]++;
    m_bufRefCountMtx[index].unlock();
    m_dqedBufsMtx.unlock();
    return true;
}

void V4l2Device::ReleaseBuf(v4l2_buffer &buf)
{
    int index = buf.index;
    m_bufRefCountMtx[index].lock();
    if(m_bufRefCount[index]>=1)
    {
        m_bufRefCount[index]--;//index对应的缓存帧不再被应用层使用，引用计数减1
    }
    m_bufRefCountMtx[index].unlock();
    Qbufs();//老数据判断是否需要清除
}

void V4l2Device::Qbufs()
{
    m_dqedBufsMtx.lock();
    for (auto it = m_dqedBufs.begin(); it != m_dqedBufs.end(); /**/)
    {
        int index = it->index;
        m_bufRefCountMtx[index].lock();//接下去使用引用计数m_bufRefCount[index]，不允许其他线程修改
        int count = m_bufRefCount[index];
        m_bufRefCountMtx[index].unlock();
        if (count<1)
        {
            Qbuf(*it);
            m_dqedBufs.erase(it);
        }
        else
        {
            ++it;
        }
    }
    m_dqedBufsMtx.unlock();
}

bool V4l2Device::Qbuf(v4l2_buffer &buf)
{
    if (ioctl(m_fdV4l, VIDIOC_QUERYBUF, &buf) == -1)
    {
        printf("VIDIOC_QUERYBUF failed\n");
        return false;
    }
    if((buf.flags&V4L2_BUF_FLAG_QUEUED)==V4L2_BUF_FLAG_QUEUED)
    {
//        qDebug()<<"buf has been queued ";
        return false;
    }
    if((buf.flags&V4L2_BUF_FLAG_DONE)==V4L2_BUF_FLAG_DONE)
    {
//        qDebug()<<"buf has been done";
        return false;
    }
    /* 将空闲的内存加入可捕获视频的队列 */
    if(ioctl(m_fdV4l, VIDIOC_QBUF, &buf) < 0)
    {
//        printf("ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n", dev->dev, __FUNCTION__, __LINE__);
//        printf("VIDIOC_QBUF failed\n");
//        qDebug()<<"VIDIOC_QBUF failed index:"<<index<<"bufRefCount:"<<m_bufRefCount[index];
//        qDebug()<<"buf.flags"<<buf.flags<<"index:"<<index;
        return false;
    }
    return true;
}

void V4l2Device::CloseDevice()
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(m_fdV4l, VIDIOC_STREAMOFF, &type);
    for (unsigned int i = 0; i < BUF_NUM; i++)
    {
        munmap(m_mmapMemorys[i].start, m_mmapMemorys[i].length);
    }
    close(m_fdV4l);
}
